﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using NUnit.Framework;
using XcoAppSpaces.Core;
using XcoAppSpaces.Contracts.Exceptions;

namespace XcoAppSpaces.Discovery.Tests
{
	[TestFixture]
	public class IntegrationTests
	{
		[Test]
		public void TestWithLocalDiscovery()
		{
			using (XcoAppSpace space1 = XcoAppSpace.Configure.UsingService<XcoDiscoveryService>())
			{
				Assert.IsTrue(space1.HasDiscoveryService());

				space1.RunWorker<TestWorker, TestWorker>();
				var worker = space1.DiscoverWorker<TestWorker>();
				Assert.IsNotNull(worker);
			}
		}

		[Test]
		public void TestWithRemoteDiscovery_WorkerHostedByServer()
		{
			using (XcoAppSpace space1 = XcoAppSpace.ConfigureWithConfigString("tcp.port=8001").UsingService<XcoDiscoveryService>(),
				space2 = XcoAppSpace.ConfigureWithConfigString("tcp.port=8002").UsingService(new XcoDiscoveryService("localhost:8001")))
			{
				space1.RunWorker<TestWorker, TestWorker>();
				var remoteWorker = space2.DiscoverWorker<TestWorker>();

				Assert.IsNotNull(remoteWorker);

				remoteWorker.Post("test");
				Thread.Sleep(100);
			}
		}

		[Test]
		public void TestWithRemoteDiscovery_WorkerHostedByOtherClient()
		{
			using (XcoAppSpace space1 = XcoAppSpace.ConfigureWithConfigString("tcp.port=8001").UsingService<XcoDiscoveryService>(),
				space2 = XcoAppSpace.ConfigureWithConfigString("tcp.port=8002").UsingService(new XcoDiscoveryService("localhost:8001")),
				space3 = XcoAppSpace.ConfigureWithConfigString("tcp.port=8003").UsingService(new XcoDiscoveryService("localhost:8001")))
			{
				space3.RunWorker<TestWorker, TestWorker>();
				var remoteWorker = space2.DiscoverWorker<TestWorker>();

				Assert.IsNotNull(remoteWorker);

				remoteWorker.Post("test");
				Thread.Sleep(100);
				space2.Dispose();
				Thread.Sleep(100);
				space3.Dispose();
				Thread.Sleep(100);
				space1.Dispose();
			}
		}

		[Test]
		public void TestWithRemoteDiscovery_WorkerHostedByClient_RequestByServer()
		{
			using (XcoAppSpace space1 = XcoAppSpace.ConfigureWithConfigString("tcp.port=8001").UsingService<XcoDiscoveryService>(),
				space2 = XcoAppSpace.ConfigureWithConfigString("tcp.port=8002").UsingService(new XcoDiscoveryService("localhost:8001")))
			{
				Thread.Sleep(100);
				space2.RunWorker<TestWorker, TestWorker>();
				var remoteWorker = space1.DiscoverWorker<TestWorker>();

				Assert.IsNotNull(remoteWorker);

				remoteWorker.Post("test");
				Thread.Sleep(100);
			}
		}

		[Test]
		public void TestWithRemoteDiscovery_WorkerHostedBySelfAsClient()
		{
			using (XcoAppSpace space1 = XcoAppSpace.ConfigureWithConfigString("tcp.port=8001").UsingService<XcoDiscoveryService>(),
				space2 = XcoAppSpace.ConfigureWithConfigString("tcp.port=8002").UsingService(new XcoDiscoveryService("localhost:8001")))
			{
				var localWorker = space2.RunWorker<TestWorker, TestWorker>();
				var remoteWorker = space2.DiscoverWorker<TestWorker>();

				Assert.IsNotNull(remoteWorker);
				Assert.AreSame(localWorker, remoteWorker);

				remoteWorker.Post("test");
				Thread.Sleep(100);
			}
		}

		[Test]
		public void TestWithStringConfig()
		{
			using (XcoAppSpace space1 = new XcoAppSpace("tcp.port=8001;disco.isserver=true"),
				space2 = new XcoAppSpace("tcp.port=8002;disco.address=localhost:8001"))
			{
				space1.RunWorker<TestWorker, TestWorker>();
				var remoteWorker = space2.DiscoverWorker<TestWorker>();
				remoteWorker.Post("test");

				Thread.Sleep(100);
			}
		}

		[Test]
		[ExpectedException(typeof(XcoConfigurationException))]
		public void TestInvalidStringConfig1()
		{
			using (XcoAppSpace space1 = new XcoAppSpace("tcp.port=8001;disco.isserver=bla"))
			{
			}
		}

		[Test]
		[ExpectedException(typeof(XcoConfigurationException))]
		public void TestInvalidStringConfig2()
		{
			using (XcoAppSpace space1 = new XcoAppSpace("tcp.port=8001;disco.isserver=true;disco.address=someotheraddress"))
			{
			}
		}

		[Test]
		[ExpectedException(typeof(XcoDiscoveryException))]
		public void TestAutomaticRemovalOfSpaceAddressAfterConnectionLoss()
		{
			using (XcoAppSpace space1 = new XcoAppSpace("tcp.port=8001;disco.isserver=true"),
				space2 = new XcoAppSpace("tcp.port=8002;disco.address=localhost:8001"))
			{
				space2.RunWorker<TestWorker, TestWorker>();
				//var remoteWorker = space2.DiscoverWorker<TestWorker>();
				//remoteWorker.Post("test");

				Thread.Sleep(100);
				space2.StopCommunication();
				Thread.Sleep(100);

				space1.DiscoverWorker<TestWorker>();
			}
		}

		[Test]
		[ExpectedException(typeof(XcoDiscoveryException))]
		public void TestErrorWhenDiscoServiceNotReachable()
		{
			using (XcoAppSpace space2 = new XcoAppSpace("tcp.port=8002;disco.address=localhost:8001"))
			{
				space2.DiscoverWorker<TestWorker>();
			}
		}

		[Test]
		[ExpectedException(typeof(XcoDiscoveryException))]
		public void TestErrorWhenDiscoServiceNotAdded()
		{
			using (XcoAppSpace space2 = new XcoAppSpace("tcp.port=8002"))
			{
				Assert.IsFalse(space2.HasDiscoveryService());
				space2.DiscoverWorker<TestWorker>();
			}
		}

		[Test]
		[ExpectedException(typeof(XcoDiscoveryException))]
		public void TestDiscoServerConnectionLoss()
		{
			using (XcoAppSpace space1 = new XcoAppSpace("tcp.port=8001;disco.isserver=true"),
				space2 = new XcoAppSpace("tcp.port=8002;disco.address=localhost:8001"))
			{
				//space2.RunWorker<TestWorker, TestWorker>();
				//var remoteWorker = space2.DiscoverWorker<TestWorker>();
				//remoteWorker.Post("test");

				Thread.Sleep(300);
				space1.StopCommunication();
				Thread.Sleep(100);

				space2.DiscoverWorker<TestWorker>();
			}
		}

		[Test]
		public void TestAutomaticReconnectToDiscoServer()
		{
			using (XcoAppSpace space2 = new XcoAppSpace("tcp.port=8002;disco.address=localhost:8001"))
			{
				Thread.Sleep(200);
				using (XcoAppSpace space1 = new XcoAppSpace("tcp.port=8001;disco.isserver=true"))
				{
					space2.RunWorker<TestWorker, TestWorker>();

					try
					{
						space1.DiscoverWorker<TestWorker>();
						Assert.Fail("Connecting should not be possible right now");
					}
					catch (XcoException)
					{
					}

					Thread.Sleep(11000); //in this time, space2 should automatically try reconnect to space1

					space1.DiscoverWorker<TestWorker>();
				}
			}
		}

		[Test]
		public void TestDiscoveryWithChangingServerAvailability()
		{
			using (var client = new XcoAppSpace("tcp.port=8002;disco.address=localhost:8001"))
			{
				//server starting after client, connecting should work
				Thread.Sleep(1000);
				using (var server = new XcoAppSpace("tcp.port=8001;disco.isserver=true"))
				{
					server.RunWorker<TestWorker, TestWorker>();

					var w = client.DiscoverWorker<TestWorker>();
					client.StopWorker(w);
					Console.WriteLine("First DiscoverWorker succeeded - OK");
				}

				//server is gone, connecting should not work
				try
				{
					client.DiscoverWorker<TestWorker>();
					Assert.Fail("Connecting should not be possible right now");
				}
				catch (XcoException)
				{
					Console.WriteLine("Second DiscoverWorker failed - OK");
				}

				//server restarted, connecting should work again
				using (var server = new XcoAppSpace("tcp.port=8001;disco.isserver=true"))
				{
					server.RunWorker<TestWorker, TestWorker>();

					client.DiscoverWorker<TestWorker>();
					Console.WriteLine("Third DiscoverWorker succeeded - OK");
				}
			}
		}

		[Test]
		public void TestWorkerWithName()
		{
			using (XcoAppSpace space1 = new XcoAppSpace("tcp.port=8001;disco.isserver=true"),
				space2 = new XcoAppSpace("tcp.port=8002;disco.address=localhost:8001"),
				space3 = new XcoAppSpace("tcp.port=8003;disco.address=localhost:8001"))
			{
				space3.RunWorker<TestWorker, TestWorker>("myworker");
				var remoteWorker = space2.DiscoverWorker<TestWorker>("myworker");
				remoteWorker.Post("test");

				Thread.Sleep(100);
			}
		}
	}
}
